<?php
/**
 * Created by 784855684@qq.com.
 * Link: https://github.com/lichtung/wshore
 * User: linzh
 * Date: 6/30/17
 * Time: 4:00 PM
 */
declare(strict_types=1);


namespace application\module\admin\addon;


use application\module\admin\addon\rbca\model\AccessModel;
use application\module\admin\addon\rbca\model\ReflectModel;
use application\module\admin\addon\rbca\model\RoleAccessModel;
use application\module\admin\addon\rbca\model\RoleModel;
use application\module\admin\addon\rbca\model\UserRoleModel;
use wshore\core\Dao;
use wshore\core\Storage;
use wshore\helper\Chars;
use wshore\throws\DatabaseException;

/**
 * Class Rbca
 *
 * @method Rbca getInstance(...$params) static
 *
 * @package application\module\admin\addon
 */
class Rbca extends Addon
{
    protected function getName(): string
    {
        return 'rbca';
    }

    protected function models(): array
    {
        return [
            AccessModel::class,
            RoleModel::class,
            ReflectModel::class,
            RoleAccessModel::class,
            UserRoleModel::class,
        ];
    }

    private $cache = [];

    protected $skipReflectModule = [
        'explorer'
    ];


    /**
     * 检查访问请安
     * @param int $uid
     * @param string $path
     * @return bool
     */
    public function checkAuthorize(int $uid, string $path): bool
    {

        if ($uid !== 1) {
            if (!isset($this->cache[$uid])) {
                $this->cache[$uid] = self::getAccessList($uid);
            }

            $auth_list = &$this->cache[$uid];

            if (isset($auth_list[$path])) {
                //在权限管理内
                $auth = $auth_list[$path];
                // 禁用关闭并且用户得到授权
                if (intval($auth['disable']) === 0 and intval($auth['authed']) === 0) {
                    return false;
                }
            }//否则，该URL可以被任何人访问
        }//放行root账户（用户ID为1,默认创建）
        return true;
    }

    /**
     * 获取客户权限列表，列表中项'authed'=0时表示未授权
     *
     * 注意:角色被禁用时该角色拥有的所有权限都会被临时禁用
     *CREATE TABLE `ws_menu` (
     * `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
     * `name` varchar(255) NOT NULL DEFAULT 'untitled' COMMENT '菜单项名称',
     * `icon` varchar(255) NOT NULL DEFAULT '' COMMENT 'fa图标',
     * `value` varchar(255) NOT NULL DEFAULT '' COMMENT '菜单项值',
     * `orderNo` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
     * `pid` int(255) unsigned NOT NULL DEFAULT '0' COMMENT '为0时表示顶级菜单，否则表示特定的菜单项的子菜单项',
     * `category` tinyint(255) unsigned NOT NULL DEFAULT '1' COMMENT '菜单类型，类型定义在menu_category中实现',
     * `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     * `disable` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否禁用',
     * `target` varchar(16) CHARACTER SET ascii NOT NULL DEFAULT '_self' COMMENT 'a标签的target属性',
     * PRIMARY KEY (`id`)
     * ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     * @param int $uid 客户ID
     * @return array 返回权限列表
     */
    public function getAccessList(int $uid): array
    {
        $sql = "SELECT a.id,a.name,a.value,a.type,`disable`,IFNULL(b.role_id,0) role_id,IFNULL(b.auth_id,0) authed 
FROM l_auth_access a
LEFT JOIN (
	SELECT rmr.rid AS role_id, rra.aid AS auth_id
	FROM l_auth_rel_user_role rmr
    INNER JOIN l_auth_role r on r.id = rmr.rid and r.`disable` = 0
	INNER JOIN l_auth_rel_role_access rra ON rra.rid = rmr.rid
	WHERE rmr.uid = {$uid}
) b ON b.auth_id = a.id;";
        $list = Dao::getInstance()->query($sql);
        $temp = [];
        foreach ($list as $item) {
            $temp[$item['value']] = $item;
        }
        return $temp;
    }

    /**
     * 反射表并添加权限
     * @return void
     * @throws DatabaseException
     */
    public function reflect2access()
    {
        $this->scan(WS_PATH_APP . 'module/admin/', true);
        //ws_ => 动态
        Dao::getInstance()->exec('INSERT INTO ws_auth_access (`name`,`value`,`disable`) 
SELECT r.path,r.path,1 FROM ws_reflect r WHERE not EXISTS (
SELECT 1 FROM ws_auth_access a where r.path = a.`value`)');
    }

    /**
     * 获取用户的全部角色
     * @param int $uid
     * @return array
     */
    public function getUserRoles(int $uid): array
    {
        $model = new UserRoleModel();
        $list = $model->getUserRoles($uid);
        return $list;
    }

    /**
     * 获取角色的全部访问权限
     * @param int $rid
     * @return array
     */
    public function getRoleAccess(int $rid): array
    {
        $model = new RoleAccessModel();
        $list = $model->getRoleAccesses($rid);
        return $list;
    }

    public function addUserRole(int $uid, int $rid): bool
    {
        $model = new UserRoleModel();
        $res = $model->insert([
            'uid' => $uid,
            'rid' => $rid,
        ]);
        return $res > 0;
    }

    public function removeUserRole(int $uid, int $rid): bool
    {
        $model = new UserRoleModel();
        $res = $model->delete([
            'uid' => $uid,
            'rid' => $rid,
        ]);
        return $res > 0;
    }

    public function addRoleAccess($rid, $aid)
    {
        $model = new RoleAccessModel();
        $res = $model->insert([
            'rid' => $rid,
            'aid' => $aid,
        ]);
        return $res > 0;
    }


    public function removeRoleAccess($rid, $aid)
    {
        $model = new RoleAccessModel();
        $res = $model->delete([
            'rid' => $rid,
            'aid' => $aid,
        ]);
        return $res > 0;
    }



    //------------------------ 反射 --------------------------------------//

    /**
     * @var ReflectModel
     */
    private $model = null;

    public function __construct()
    {
        parent::__construct();
        $this->model = new ReflectModel();
    }

    /**
     * 扫描目录
     * @param string $scandir 待扫描目录
     * @param bool $refresh 是否刷新
     * @return array
     */
    public function scan(string $scandir, bool $refresh = false): array
    {
        if ($refresh) {
            $result = Storage::readdir($scandir, true);
            foreach ($result as $modulename => $modulepath) {
                if (Chars::isEndWith($modulename, 'controller')) {
                    $modulename = str_replace('/', '\\', trim(Chars::stripTail('admin\\' . $modulename, 'controller'), '\\/'));
                    if (in_array($modulename, $this->skipReflectModule)) continue;# 跳过explorer
                    $controllers = Storage::readdir($modulepath, false);
                    if ($controllers) foreach ($controllers as $name => $path) {
                        $controllerName = Chars::stripTail(ltrim($name, '\\/'), '.php');
                        if ($modulename) {
                            $controllerFullName = '\\application\\module\\' . $modulename . '\\controller\\' . $controllerName;
                        } else {
                            $controllerFullName = '\\application\\module\\controller\\' . $controllerName;
                        }
                        if (!class_exists($controllerFullName)) continue;
                        foreach ((array)$this->fetchPublicMethods($controllerFullName) as $name) {
                            # 以'_'开头的方法将被忽略
                            if (strpos($name, '_') === 0) continue;
                            $this->save(str_replace('\\', '/', $modulename), $controllerName, $name);
                        }
                    }
                }
            }
        }
        return $this->model->getlist();
    }

    /**
     * 修改备注
     * @param int $id
     * @param string $comment
     * @return bool
     */
    public function updateComment(int $id, string $comment): bool
    {
        try {
            $ar = new ReflectModel($id);
            $ar->comment = (string)$comment;
            if (!$ar->save(false)) {
                $this->error = $ar->error();
                return false;
            }
            return true;
        } catch (DatabaseException $exception) {
            $this->error = $exception->getMessage();
            return false;
        }
    }

    /**
     * 保存到数据库
     * @param string $modules
     * @param string $controller
     * @param string $method
     * @return bool
     */
    public function save(string $modules, string $controller, string $method): bool
    {
        $path = $modules . '/' . $controller;
        $this->model->module = $modules;
        $this->model->controller = $controller;
        $this->model->method = $method;
        $this->model->path = trim($path . '/' . strtolower($method), '/');
        if (!$this->model->save(true)) {
            return false;
        }
        return true;
    }

    /**
     * @warning 获取类的全部方法,包括继承自父类的方法(访问)
     * @param $classname
     * @return array
     */
    private function fetchPublicMethods(string $classname): array
    {
        $array = [];
        $classname = rtrim(str_replace('/', '\\', $classname), '\\');
        $instance = new \ReflectionClass($classname);
        $methods = $instance->getMethods(\ReflectionMethod::IS_PUBLIC);
        foreach ($methods as $method) {
            $name = $method->getName();
            if (0 === strpos($name, '_')) continue;//以单下划线或者双下划线开头直接忽略
            $array[] = $name;
        }
        return $array;
    }
}